M. Fawcett - 11/30/2021
This Notebook builds an interactive map of Sears kit homes using the Folium package
# Load libraries
import folium
from folium import plugins
import ipywidgets
import geopy
import numpy as np
import pandas as pd
import geopandas as gpd
import json
# Get list of Ohio kit home coordinates
# Read previously saved results on disk back to a dataframe
geocoded_results_df = pd.read_pickle('geocoded_results.pkl')
# Only keep rows that were successfully geocoded
geocoded_results_df = geocoded_results_df[geocoded_results_df["MATCH_INDICATOR"] == "Match"]
# Convert geography code values from numeric to string
geocoded_results_df['FIPS_STATE'] = geocoded_results_df['FIPS_STATE'].astype(int).astype(str)
geocoded_results_df['FIPS_COUNTY'] = geocoded_results_df['FIPS_COUNTY'].astype(int).astype(str)
geocoded_results_df['CENSUS_TRACT'] = geocoded_results_df['CENSUS_TRACT'].astype(int).astype(str)
# Left pad geograpgy values wit zeros
geocoded_results_df['FIPS_STATE'] = geocoded_results_df['FIPS_STATE'].apply('{:0>2}'.format)
geocoded_results_df['FIPS_COUNTY'] = geocoded_results_df['FIPS_COUNTY'].apply('{:0>3}'.format)
geocoded_results_df['CENSUS_TRACT'] = geocoded_results_df['CENSUS_TRACT'].apply('{:0>6}'.format)
# Create a unique geographic identifier by combining state, county and cenus tract code for each row.
geocoded_results_df["GeoID"] = geocoded_results_df["FIPS_STATE"] \
+ geocoded_results_df["FIPS_COUNTY"] \
+ geocoded_results_df["CENSUS_TRACT"]
# Split the LONG_LAT column into separate Longitude and Latitude columns
geocoded_results_df[['Longitude', 'Latitude']] = geocoded_results_df['LONG_LAT'].str.rsplit(',', 1, expand=True)
geocoded_results_df.head()
# Build a list containing all the coordinates so they be plotted on the map
locations = geocoded_results_df[['Latitude', 'Longitude']]
locationlist = locations.values.tolist()
len(locationlist)
# An example of one point in the kit home location list
locationlist[7]
# map with location start point, zoom level, size, and distance scale
# zoom in by using a larger number for zoom_start
# Center of Ohio 40.367474, -82.996216
Ohio_map = folium.Map(location=[40.367474, -82.996216], zoom_start=7, width=900, height=550, control_scale=True)
Ohio_map
Regardless of the coordinate system of the actual map displayed, all coordinates passed to Leaflet functions/methods are always EPSG:4326.
To see what styles (weight, color,opacity etc.) can be applied to features in a layer, see https://leafletjs.com/reference-1.0.3.html#path
# map with location start point, zoom level, size, and distance scale
# Center of Ohio 40.367474, -82.996216
Ohio_map = folium.Map(location=[40.367474, -82.996216], zoom_start=7, width=900, height=550, control_scale=True)
# Ohio_map
marker_cluster = plugins.MarkerCluster().add_to(Ohio_map) #
# Define a function that can be called when a geojson layer file is added that modifies its appearance.
def stateOutline_style(feature):
return {
"weight": 5,
"color": "#CB8427",
"fill": False,
"dashArray": '10,20'
}
# Add a layer showing the outline of Ohio using the style function defined above.
# folium.GeoJson('Ohio Outline.geojson', name='Ohio outline', style_function = stateOutline_style).add_to(Ohio_map)
# Define a function that can be called when a geojson layer file is added that modifies its appearance.
def railDistance_style(feature):
return {
"weight": 2,
"color": "red",
}
# Add geojson file proximity to railroad to map
# folium.GeoJson('Distance to Rail.geojson', name='Nearest Railroad', style_function = railDistance_style).add_to(Ohio_map)
# Define a function that can be called when a geojson layer file is added that modifies its appearance.
def censusTract_style(feature):
return {
"weight": 1,
"color": "red",
"fill": False,
}
# Add geojson file census tracts to map using the style function defined above.
# folium.GeoJson('Ohio Census Tracts.geojson', name='Ohio Census Tracts', style_function = censusTract_style).add_to(Ohio_map)
# Add kit home location markers
for point in range(0, len(locationlist)):
try:
folium.CircleMarker(
location = locationlist[point],
radius = 4,
weight = 2,
color = "red",
fill = True,
fill_color = "white",
fill_opacity = 1.0
# popup=mapping_data_df['Model'][point] + ": " + mapping_data_df['ADDRESS_OUT'][point],
).add_to(marker_cluster)
except Exception: # not all addresses could be geocoded so skip them if coordinates are missing
pass
# add layer control to map (allows layer to be turned on or off)
# folium.LayerControl().add_to(Ohio_map)
# display map
# Ohio_map
Color code the Census Tracts according to the percent of structures built in 1939 and earlier. Cite https://towardsdatascience.com/how-to-step-up-your-folium-choropleth-map-skills-17cf6de7c6fe
# Load structure age data generated by this Notebook: Sears Kit Home Location Analysis
tract_yearBuilt_df = pd.read_csv("tract_yearBuilt_df.csv")
tract_yearBuilt_df.head()
# Find the values of Percent Built 1939 and Earlier, sorted, to look for problem values.
# Tract numbers like 99... and 98... are bodies of water or areas without countable population
tract_yearBuilt_df[tract_yearBuilt_df["Percent Built 1939 and Earlier"]< 0 ]
# Remove those rows
tract_yearBuilt_df = tract_yearBuilt_df[tract_yearBuilt_df["Percent Built 1939 and Earlier"] >= 0 ]
census_tract_geo_df = gpd.read_file("Ohio Census Tracts.geojson")
census_tract_geo_df
ct = list(census_tract_geo_df.GEOID.values)
print("Number of census tracts in the geojson file of boundaries is",len(ct))
census_tract_geo_df.info()
ct2 = list(tract_yearBuilt_df.GeoID.values)
print("Number of census tracts in the year structure built dataset is",len(ct2))
Both counts are the same so no cleanup of missing census tracts is needed.
# Rename the geoid in one file to match the other
tract_yearBuilt_df = tract_yearBuilt_df.rename(columns = {"GeoID":"GEOID"})
tract_yearBuilt_df.info()
# Convert the GEOID column to an object type
tract_yearBuilt_df = tract_yearBuilt_df.astype({"GEOID": str})
final_df = tract_yearBuilt_df.merge(census_tract_geo_df, on = "GEOID")
final_df.head()
folium.Choropleth?
with open('Ohio Census Tracts.geojson') as f:
geojson_censustracts = json.load(f)
geojson_censustracts["features"][0]
# om = folium.Map(location=[40.367474, -82.996216], zoom_start=7, width=900, height=550, control_scale=True)
# Set the parameters for the Choropleth
# Folium allows a number of formats for the "geo_data" parameter. It can be the name of a geojson file
# on local disk, a URL to a file on the Web, or the name of a pandas dataframe in memory that has been loaded
# with a geojson file. The disk file or URL names need to be surrounded by quote marks. The dataframe
# name does not need quote marks.
# Regardless of which format of "geo_data" used, the "key_on" parameter must be of the form
# "feature.id" or "feature.properties.<a column name>". I used the second format here because I used
# the dataframe method of passing the geo_data value. If I had used a json file name or URL path
# in the geo_data parameter I would have had to add an "id" feature to the json data, similar to the following:
# for i in geojson_data['features']:
# i['id'] = i['properties']['NAME'] # 'NAME' being a column of unique values in the json
folium.Choropleth(
geo_data=census_tract_geo_df,
name="choropleth",
data=final_df,
columns=["GEOID", "Percent Built 1939 and Earlier"],
key_on='feature.properties.GEOID',
fill_color='YlGn',
fill_opacity=0.5,
line_opacity=0.5,
legend_name='Structure age',
highlight=True
).add_to(Ohio_map)
# add layer control to map (allows layer to be turned on or off)
folium.LayerControl().add_to(Ohio_map)
Ohio_map